package com.acmen.easyapi;

import com.acmen.easyapi.factory.ApiFactory;
import com.acmen.easyapi.generator.DataGenerator;
import com.acmen.easyapi.model.Api;
import com.acmen.easyapi.model.App;
import com.acmen.easyapi.model.Dictionary;
import com.acmen.easyapi.model.Response;
import com.acmen.easyapi.validation.ValidatorNoteAppender;
import com.alibaba.fastjson.JSON;
import com.acmen.easyapi.enums.BasicCode;
import com.acmen.easyapi.util.DictionaryUtil;
import com.acmen.easyapi.util.Md5Util;
import com.acmen.easyapi.util.SpringContextUtil;
import com.acmen.easyapi.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 接口同步器
 * @author acmen
 */
@Component
public class ApiSynchronizer implements ApplicationRunner, ApplicationContextAware {
    public ConfigurableApplicationContext applicationContext;

    @Value("${server.port}")
    private int port;
    @Value("${spring.application.name:}")
    private String appName;
    @Value("${spring.application.route:}")
    private String route;
    @Value("${spring.application.note:}")
    private String appNote;
    @Value("${spring.profiles.active:}")
    private String env;

    @Value("${acmen.api.responseCodeEnum:}")
    private String responseCodeEnum;

    @Value("${acmen.easyapi.serverAddr:}")
    private String serverAddr;



    private static final Logger log = LoggerFactory.getLogger(ApiSynchronizer.class);

    @Override
    public void run(ApplicationArguments args){
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();
        new Thread(()->{
            sync(beanFactory);
        }).start();
    }

    /**
     * 执行同步
     *
     * @return
     */

    public void sync(DefaultListableBeanFactory beanFactory) {
        if(StringUtil.isEmpty(env)){
            env = System.getProperty("spring.profiles.active");
        }
        if(!"uat".equals(env)){
            log.info("非uat环境不同步接口文档");
            return;
        }
        new Thread(()->{
            List<Api> apis = new ArrayList<>();
            beanFactory.getBeansOfType(ApiFactory.class).forEach((k, v)->{
                apis.addAll(v.createApis(beanFactory));
            });
            if(apis.isEmpty()){
                log.debug("应用没有要同步的接口");
                return;
            }
            apis.forEach(api -> {
                String id = Md5Util.md5(appName+api.getRequestMethod() + api.getPath());
                api.setId(id);
            });
            App app = createApp(appName,route,appNote,apis, responseCodeEnum);
            log.info("同步文档开始……");
            postApiDoc(app);
        }).start();
    }

    private void postApiDoc(App app) {
        String creator = System.getProperty("user.name");
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.set("creator",creator);
        headers.set("port",String.valueOf(port));
        headers.set("env",env);
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity httpEntity = new HttpEntity(JSON.toJSON(app), headers);

        long sleepTime = 1000L*10;
        while (true){
            try{
                String basePath = serverAddr;

                if(basePath != null){
                    String url = basePath + "/app/sync";
                    ResponseEntity<Response> responseEntity = restTemplate.postForEntity(url, httpEntity, Response.class);
                    if(responseEntity.getStatusCode().value()==200){
                        Response res = responseEntity.getBody();
                        if(res.isSuccess()){
                            log.info("同步文档至["+basePath+"]成功，接口文档地址："+basePath + "/app/"+app.getName());
                        }else{
                            log.warn("同步文档至["+basePath+"]失败，"+res.getMessage());
                        }
                        break;
                    }
                }else{
                    log.error("接口管理平台不在线，同步文档异常,{}，"+sleepTime/1000+"秒后再尝试……");
                    try {
                        Thread.sleep(sleepTime);
                        sleepTime = sleepTime*2;
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }
                }
            }catch (Exception e){
                log.error("同步文档异常,{}，"+sleepTime/1000+"秒后再尝试……",e);
                try {
                    Thread.sleep(sleepTime);
                    sleepTime = sleepTime*2;
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
            }
        }
    }

    /**
     * 构造接口应用
     * @param name
     * @param note
     * @param apis
     * @param responseCodeEnum
     * @return
     */
    public App createApp(String name,String route, String note, List<Api> apis, String responseCodeEnum) {
        App app = new App(name,port,route,note,apis);
        List<Class> classes = new ArrayList<>();
        for (String className : responseCodeEnum.replace(",", ";").split(";")) {
            if(StringUtil.isNotEmpty(className)){
                try {
                    classes.add(Class.forName(className));
                } catch (ClassNotFoundException e) {
                    log.warn("类名["+className+"]反射失败");
                }
            }
        }
        if(classes.stream().filter(c-> BasicCode.class.isAssignableFrom(c)).findAny().isPresent() == false){
            classes.add(BasicCode.class);
        }
        List<Dictionary> responseCodes = new ArrayList<>();
        classes.forEach(e->{
            responseCodes.addAll(DictionaryUtil.getDictsFromEnum(e));
        });
        app.setResponseCodes(responseCodes);
        app.setDataGeneratorNotes(getDataGeneratorNotes());
        ValidatorNoteAppender validatorNoteAppender = SpringContextUtil.getBean(ValidatorNoteAppender.class);
        if(validatorNoteAppender!=null){
            app.setValidatorNotes(validatorNoteAppender.getAllNotes());
        }
        return app;
    }

    private List<String> getDataGeneratorNotes() {
        Map<String, DataGenerator> dataGeneratorMap = SpringContextUtil.getBeansOfType(DataGenerator.class);
        List<String> notes = new ArrayList<>();
        for (Map.Entry<String, DataGenerator> entry : dataGeneratorMap.entrySet()) {
            DataGenerator generator = entry.getValue();
            notes.add(generator.getNote());
        }
        return notes;
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = (ConfigurableApplicationContext) applicationContext;
    }
}
